package thinkingInJava.enumerated;

//: enumerated/VendingMachine.java
//{Args: VendingMachineInput.txt}
import java.util.*;

import thinkingInJava.arrays.Generator;
import thinkingInJava.YTool.TextFile;

import static thinkingInJava.enumerated.Input.*;
import static thinkingInJava.YTool.YPrint.*;

enum Category {
	MONEY(NICKEL, DIME, QUARTER, DOLLAR),
	ITEM_SELECTION(TOOTHPASTE, CHIPS, SODA, SOAP),
	QUIT_TRANSACTION(ABORT_TRANSACTION),
	SHUT_DOWN(STOP);
	private Input[] values;
	Category(Input... types) { values = types; }	
	private static EnumMap<Input,Category> categories =
			new EnumMap<Input,Category>(Input.class);
	static {
		for(Category c : Category.class.getEnumConstants())
			for(Input type : c.values)
				categories.put(type, c);
	}
	public static Category categorize(Input input) {
		return categories.get(input);
	}
}	

public class VendingMachine {
	private static State state = State.RESTING;
	private static int amount = 0;
	private static Input selection = null;
	enum StateDuration { TRANSIENT } // Tagging enum
	enum State {
		RESTING {
			void next(Input input) {
				switch(Category.categorize(input)) {
				case MONEY:
					amount += input.amount();
					state = ADDING_MONEY;
					break;
				case SHUT_DOWN:
					state = TERMINAL;
				default:
				}
			}
		},	
		ADDING_MONEY {
			void next(Input input) {
				switch(Category.categorize(input)) {
				case MONEY:
					amount += input.amount();
					break;
				case ITEM_SELECTION:
					selection = input;
					if(amount < selection.amount())
						print("Insufficient money for " + selection);
					else state = DISPENSING;
					break;
				case QUIT_TRANSACTION:
					state = GIVING_CHANGE;
					break;
				case SHUT_DOWN:
					state = TERMINAL;
				default:
				}
			}
		},	
		DISPENSING(StateDuration.TRANSIENT) {
			void next() {
				print("here is your " + selection);
				amount -= selection.amount();
				state = GIVING_CHANGE;
			}
		},
		GIVING_CHANGE(StateDuration.TRANSIENT) {
			void next() {
				if(amount > 0) {
					print("Your change: " + amount);
					amount = 0;
				}
				state = RESTING;
			}
		},	
		TERMINAL { void output() { print("Halted"); } };
		private boolean isTransient = false;
		State() {}
		State(StateDuration trans) { isTransient = true; }
		void next(Input input) {
			throw new RuntimeException("Only call " +
					"next(Input input) for non-transient states");
		}
		void next() {
			throw new RuntimeException("Only call next() for " +
					"StateDuration.TRANSIENT states");
		}
		void output() { print(amount); }
	}	
	static void run(Generator<Input> gen) {
		while(state != State.TERMINAL) {
			state.next(gen.next());
			while(state.isTransient)
				state.next();
			state.output();
		}
	}
	public static void main(String[] args) {
		Generator<Input> gen = new RandomInputGenerator();
		if(args.length == 1)
			gen = new FileInputGenerator(args[0]);
		run(gen);
	}
}	

//For a basic sanity check:
class RandomInputGenerator implements Generator<Input> {
	public Input next() { return Input.randomSelection(); }
}

//Create Inputs from a file of ';'-separated strings:
class FileInputGenerator implements Generator<Input> {
	private Iterator<String> input;
	public FileInputGenerator(String fileName) {
		input = new TextFile(fileName, ";").iterator();
	}
	public Input next() {
		if(!input.hasNext())
			return null;
		return Enum.valueOf(Input.class, input.next().trim());
	}
} /* Output:
25
50
75
here is your CHIPS
0
100
200
here is your TOOTHPASTE
0
25
35
Your change: 35
0
25
35
Insufficient money for SODA
35
60
70
75
Insufficient money for SODA
75
Your change: 75
0
Halted
 *///:~
